package ir;


import org.apache.axis.client.Stub;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.xml.rpc.ServiceException;

public class StubNode extends ClassGenerator  {

  private List<StubMethodNode> methodNodes;
  private Class<Object> locator;
  private String serviceName;
  
  /**
   * Descend into Stub. Ignore all static and private methods.
   * Generate method and field nodes.
   * @param stub
   * @param locator 
   * @param serviceName 
   */
  public StubNode(Class<Object> stub, Class<Object> locator, String serviceName) {
    
    super(stub);
    
    this.locator = locator;
    this.serviceName = serviceName;
    methodNodes = new ArrayList<StubMethodNode>();
    
    // gets all methods from the stub eligible for conversion:  non-static public ones
    Set<Method> eligibleMethods = getEligibleMethods(stub);
    for (Method m : eligibleMethods)
      methodNodes.add(new StubMethodNode(m));

  }
  
  private Set<Method> getEligibleMethods(Class<Object> stub) {

    HashSet<Method> methods = new HashSet<Method>();
    
    Method[] declaredMethods = stub.getDeclaredMethods();
    for (Method m : declaredMethods) {
      int modifiers = m.getModifiers();
      if (! Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
        methods.add(m);
      }
    }
    
    return methods;
  }

  
  
  @Override
  public Set<DataNode> getRequiredDataNodes() {
    
    // Retrieve Data Types assumed to be pre-requisite;
    // goes thru all methods for return and argument types.
    Set<Class<Object>> requiredTypes = getRequiredTypes();
    Set<DataNode> requiredNodes = new HashSet<DataNode>();
    
    // convert to DataNodes
    for (Class<Object> dataClass : requiredTypes) {
      
      // get component type if it's an array. 
      Class<Object> candidateClass;
      if (dataClass.isArray()) {
        candidateClass = (Class<Object>) dataClass.getComponentType();
      }
      else candidateClass = dataClass;
      
      // filter for primitives and strings
      if (! candidateClass.isPrimitive() && ! candidateClass.equals(String.class)) {
        
          // Filter for Axis stuff
          if (! candidateClass.getName().contains("axis")) {

            DataNode dataNode;
            
            //TODO new class extending datanode that works exclusively for these enum types
            if (candidateClass.getConstructors().length == 0 ) {
//              System.out.println("StubNode: get rwequired data nodes:" + candidateClass.getSimpleName());
              dataNode = new EnumDataNode(candidateClass);
            }
            else
              dataNode = new DataNode(candidateClass);
            
            
            
            requiredNodes.add(dataNode);
          }
      }
      
    }
    
    /**
     * Compute closure. expand the set of dependencies until the set stops growing. 
     */
    
    // seed with direct dependencies of the Stub
    Set<DataNode> currTypeSet = new HashSet<DataNode>();
    currTypeSet.addAll(requiredNodes);
    Set<DataNode> newSet = new HashSet<DataNode>();
    
    do {
      
      //      add all of the computed set to the current set
      currTypeSet.addAll(newSet);
      
      //     get required types of the set
      //     add those types to the newly computed type set
      for (DataNode n : currTypeSet) {
        Set<DataNode> requiredDataNodes = n.getRequiredDataNodes();
        newSet.addAll(requiredDataNodes);
      
      }
    }
    //    while computed set is not a subset of the current set 
    while (! currTypeSet.containsAll(newSet));
    
    requiredNodes.addAll(currTypeSet);
    
    return requiredNodes;
  }
  
  

  
  /**
   * Generate the GWT-Stub glue pseudostub.
   * 
   * General form:
   * 
   * public class GwtOrderServiceSoapBindingStub {
   * 
   *    
   *    OrderServiceSoapBindingStub stub; // the axis stub
   *    
   *    public GwtOrderServiceSoapBindingStub() {
   *        SiteServiceServiceLocator deSTELocator = new OrderServiceServiceLocator();
   *       deSTEService = (SiteServiceSoapBindingStub) deSTELocator.getSiteService();

            // Credential Information
            deSTEService.setUsername( "super" );
            deSTEService.setPassword( "doubleclick" );

   *    }
   * 
   *    public GwtOrderData[] getOrdersByBilingNUmber(String billingNumber) {
   *        OrderData[] orderData = stub.getOrdersByBillingNumber();
   *        return Soap2Gwt(orderData);
   *    }
   *    
   *    public void saveOrder (GwtOrderData in) {
   *        stub.saveOrder( Gwt2Soap(in) );
   *    }
   *    
   *    // maps OrderData to GwtOrderData, etc.
   *    OrderData Gwt2Soap( GwtOrderData in) {
   *        OrderData d = new OrderData();
   *        d.setName( in.getName() );
   *         ... etc
   *    }
   *    
   *    // maps GwtOrderData to OrderData, etc.
   *    GwtOrderData Soap2Gwt ( OrderData d ) {
   *        GwtOrderData gd = new GwtOrderData();
   *        gd.setID( d.getID());
   *        gd.setName( d.getName());
   *         ... etc 
   *    }
   * }
   * 
   */
  @Override
  public String gen() {
    String gen = "\n";

    gen += INDENT + underlier.getSimpleName() + " stub;\n";
    gen += generateConstructor() + "\n\n";
    gen += generateMethods() + "\n\n";
    
    gen = encapsulate(gen);
    
    gen = getImports() + gen;
    
    gen = getImplPackageDeclaration() + gen;
    
    return gen + "\n\n";
  }
  
  /** Produces the Service Interface
   * The interface is of the form:
   * 
   * public interface SiteService extends RemoteService {
   *    public GwtSiteData[] getSites();
   *        ... etc ...
   * }   
   *
   * @return the code for the service interface class
   */
  public String genServiceInterface() {
    String iface = "";
    
    String iface_head = "public interface " + serviceName + " extends RemoteService "; 
    
    String iface_body = "";
    for (StubMethodNode m : methodNodes) {
      iface_body += INDENT + m.genServiceInterfaceSignature() + "\n";
    }
    
    String imports = super.getImports();
    imports += "import com.google.gwt.user.client.rpc.RemoteService;\n";
    
    iface = imports + "\n" + iface_head + " {\n" + iface_body + "}\n";

    iface = getPackageDeclaration() + iface;
    return iface;
  }
  
  public String getServiceName() {
    return serviceName;
  }
  
  public String getServiceAsyncName() {
    return serviceName + "Async";
  }
  
  /**
   * Generates the asynchronous Service Interface.
   * The interface is of the form:
   * 
   * public interface SiteServiceAsync {
   *    public void getSites(AsyncCallback callback);
   *        ... etc ...
   * }
   * @return the code for the service interface class
   */
  public String genServiceAsyncInterface() {
    String iface = "";
    
    String iface_head = "public interface " + serviceName + "Async"; 
    
    String iface_body = "";
    for (StubMethodNode m : methodNodes) {
      iface_body += INDENT + m.genServiceAsyncInterfaceSignature() + "\n";
    }

    String imports = super.getImports();
    imports += "import com.google.gwt.user.client.rpc.AsyncCallback;\n";
    
    iface = imports + "\n" + iface_head + " {\n" + iface_body + "}\n";
    
//    String package_declare = "package " + getPackage();
    iface = getPackageDeclaration() + iface;
    
    return iface;
  }

  private String getPackageDeclaration() {
    return "package " + getPackage() + ";\n\n";
  }
  
  private String getImplPackageDeclaration() {
    return "package " + getImplPackage() + ";\n\n";
  }

  //TODO Stub constructor 
  private String generateConstructor() {
    String constructor = "";
    constructor += INDENT + "public " + getClassname() + "() {\n";
//    constructor += "    stub = new " + stub.getSimpleName() + "(); \n}";
    
    String serviceName = underlier.getSimpleName().replaceAll("SoapBindingStub", "");
    String serviceLocator = underlier.getSimpleName().replaceAll("SoapBindingStub", "ServiceLocator");
    constructor += INDENT + INDENT + serviceLocator + " service = new " + serviceLocator + "();\n";
    constructor += "try {";
    constructor += INDENT + INDENT + "stub = (" + underlier.getSimpleName() + ") service.get" + serviceName + "();\n";
    constructor += INDENT + INDENT + "stub.setUsername(\"super\"); stub.setPassword(\"doubleclick\");";
    constructor += "} catch (ServiceException e) { e.printStackTrace(); }";
    constructor += INDENT + "}\n";
    
    return constructor;
  }

  private String encapsulate(String body) {
    String classString = "";
    
    classString += "public class " + getClassname() + 
      " extends RemoteServiceServlet implements " + serviceName + " {\n" + body + "}" ;
    return classString;
  }
  
  public String getClassname() {
//    return "Gwt" + underlier.getSimpleName();
    return serviceName + "Impl";
  }
  
  /**
   * Fetches all the imports from required data nodes (All generated classes)
   *  and appends the imports required on the SOAP side.
   */
  @Override
  protected String getImports() {

    // get all super imports
    String imports = super.getImports();
    
    // get all the types required. gonna need these.
    Set<DataNode> requiredDataNodes = getRequiredDataNodes();
    for (DataNode d : requiredDataNodes) {
      imports += "import " + d.getUnderlier().getCanonicalName() + ";\n";
    }

    // TODO fetch from translatorgenerator, not here... arg.
    imports += "import " + "com.google.server.translator.Translator;\n";
    imports += "import " + underlier.getCanonicalName() + ";\n";
    imports += "import " + locator.getCanonicalName() + ";\n";
    imports += "import " + ServiceException.class.getCanonicalName() + ";\n";
    imports += "import com.google.gwt.user.server.rpc.RemoteServiceServlet;\n";

    // import service class
    imports += "import " + getPackage() + "." + getServiceName() + ";\n";
    
    imports += getExceptionTypeImports();
    return imports;
  }
  
  private String getExceptionTypeImports() {
    
    String exceptionImports = "";
    Set<Class<?>> exceptionTypes =  new HashSet<Class<?>>();
    for (StubMethodNode m : methodNodes) {
      exceptionTypes.addAll(m.getRequiredExceptions());
    }
    
    for (Class<?> exception : exceptionTypes) {
     exceptionImports += "import " + exception.getCanonicalName() + ";\n"; 
    }
    
    return exceptionImports;
  }

  private String generateMethods() {
    
    // foreach method
    //      make sig: if anything in Return or Params is eligible for
    //                  translation, do it. 
    //      invoke the equivalent existing stub method using any params.
    //      assign to a returnable, and make sure to translate the returnable.
    String gen = "";
    for (Generator n : methodNodes) {
      gen += n.gen();
    }
    
    return gen;
  }
  
  /**
   * Returns package of the service interfaces.
   */
  @Override
  public String getPackage() {
    return "com.google.client.services";
  }
  
  public String getImplPackage() {
    return "com.google.server";
  }
  

}
